package org.rich.charlesmurphy.model;

import java.util.Date;

import org.ddevil.data.Filter;
import org.ddevil.data.SortOrder;
import org.ddevil.data.set.BasicKeyedDataSet;
import org.ddevil.data.set.BasicSortableDataSet;
import org.ddevil.data.set.BeanDataSet;
import org.ddevil.data.set.DataSet;
import org.ddevil.data.set.KeyedDataSet;
import org.ddevil.data.set.SortableDataSet;
import org.ddevil.data.util.DoubleEqualsFilter;
import org.ddevil.data.util.StopWatch;
import org.rich.charlesmurphy.core.BaseObject;
import org.rich.charlesmurphy.core.CMIds;
import org.rich.charlesmurphy.core.CharliesUtils;
import org.rich.charlesmurphy.core.analyze.RequestSetAnalyzer;
import org.rich.charlesmurphy.core.analyze.TimeBasedAnalyzer;
import org.rich.charlesmurphy.ui.LabelFactory;

/**
 * The base model object that backs an analysis session.
 *
 * TODO a lot of things that are calculated here could be cached, for re-requests.
 *
 * @author Rich O'Connell
 */
public class CharlieMurphyModel extends BaseObject {

	private DataSet<Request> coreData;
	private DataSet<RequestGroup> groupedData;
	private DataSet<ResourceStats> resourceData;
	private String sessionName;

	private RequestSetAnalyzer requestSetAnalyzer;

	/**
	 * @param coreData
	 * @param groupedData
	 */
	public CharlieMurphyModel(DataSet<Request> coreData, boolean filterAjax) {
		requestSetAnalyzer = new TimeBasedAnalyzer();
		if (filterAjax) {
			requestSetAnalyzer.addFilter(new Filter<Request>() {
				public boolean accept(Request request) {
					return request.getResourceType() != ResourceType.AJAX;
				}
			});
		}

		setData(coreData);
	}

	/**
	 * @param coreData the coreData to set
	 */
	public void setData(DataSet<Request> inputData) {
		this.coreData = inputData;/*buildCoreData(inputData);*/
		StopWatch clock = new StopWatch();
		getLog().debug("Building page group data...");
		this.groupedData = buildGroupedData(coreData);
		getLog().debug("Done. Took: " + clock.readReset());
		getLog().debug("Building resource data...");
		this.resourceData = buildResourceData(groupedData);
		getLog().debug("Done. Took: " + clock.readReset());

	}

	/**
	 * Extract the core data set from the given input data.
	 * The core data set is the basis from which all other data sets are build.
	protected DataSet<Request> buildCoreData(DataSet<Data> in) {
		BeanDataSet<Request> rv = new BeanDataSet<Request>();

		for (int i = 0; i < in.size(); i++) {
			Data inRec = in.exportRecord(i);
			Request reqRec = new Request();
			reqRec.copyDataFrom(inRec);
			rv.importRecord(reqRec);
		}
		return rv;
	}
	 */

	protected DataSet<ResourceStats> buildResourceData(DataSet<RequestGroup> groupData) {
		KeyedDataSet<ResourceStats> resources = new BasicKeyedDataSet<ResourceStats>(
				new BeanDataSet<ResourceStats>(),
				"resource." + CMIds.METHOD, "resource." + CMIds.PATH);

		for (RequestGroup rg : groupData) {
			//
			handleResourceAdd(resources, rg.getOriginator(), rg);
			for (Request r : rg.getChildren()) {
				handleResourceAdd(resources, r, rg);
			}
		}
		return resources;
	}

	private static void handleResourceAdd(KeyedDataSet<ResourceStats> resources, Request r,  RequestGroup rg) {
		// TODO terribly inefficient for just a lookup
		// the keyed data set needs to have a better lookup mechanism
		ResourceStats stats = new ResourceStats(r, rg);
		if (resources.contains(stats)) {
			resources.exportRecord(stats).addSample(r, rg);
		}
		else {
			resources.importRecord(stats);
		}
	}


	/**
	 * Build the group data set from the core data set.
	 * The group data set groups requests based on the originator page.
	 */
	protected DataSet<RequestGroup> buildGroupedData(DataSet<Request> coreData) {
		DataSet<RequestGroup> grouped = requestSetAnalyzer.analyze(coreData);
		getLog().info("Done creating Grouped Request records.");
		return grouped;
	}

	/**
	 * Group data by page.
	 * @return
	 */
	public DataSet<PageItem> getPageData(String sortId) {
		DataSet<RequestGroup> data = groupedData;
		DataSet<PageItem> pages = new BeanDataSet<PageItem>();

		for (int i = 0; i < data.size(); i++) {
			RequestGroup group = data.exportRecord(i);
			PageItem pi = findInSet(pages, group);
			if (pi == null) {
				pi = new PageItem(group.getPath(), group.getMethod());
				pi.addGroup(group);
				pages.importRecord(pi);
			}
			else {
				pi.addGroup(group);
			}
		}

		SortableDataSet<PageItem> rapper = new BasicSortableDataSet<PageItem>(pages);

		rapper.setSort(sortId, SortOrder.DESCENDING);

		return rapper;
	}

	private PageItem findInSet(DataSet<PageItem> set, RequestGroup group) {
		for (int i = 0; i < set.size(); i++) {
			PageItem pi = set.exportRecord(i);
			if (pi.getPageName().equals(group.getPath())
					&& pi.getMethod().equals(group.getMethod())) {
				return pi;
			}
		}
		return null;
	}

	// all
	public DataSet<Request> getRequestsByType(final ResourceType type) {
		BasicSortableDataSet<Request> filtered = new BasicSortableDataSet<Request>(coreData);
		filtered.setFilter(CMIds.RESOURCE_TYPE, new DoubleEqualsFilter(type));
		return filtered;
	}

	// distinct
	public DataSet<ResourceStats> getResourcesByType(final ResourceType type) {
		BasicSortableDataSet<ResourceStats> filtered = new BasicSortableDataSet<ResourceStats>(resourceData);
		if (type != null) {
			filtered.setFilter("resource." + CMIds.RESOURCE_TYPE, new DoubleEqualsFilter(type));
		}
		return filtered;
	}

	/**
	 * @return the coreData
	 */
	public DataSet<Request> getCoreData() {
		return coreData;
	}

	/**
	 * @return the groupedData
	 */
	public DataSet<RequestGroup> getGroupedData() {
		return groupedData;
	}

	/**
	 * @return the groupedData
	 * TODO deprecate? should have the info needed here already in side the resource stats obj
	 */
	public DataSet<RequestGroup> findGroupsThatRequestedResource(Resource resource) {
		BeanDataSet<RequestGroup> out = new BeanDataSet<RequestGroup>();
		for (RequestGroup group : groupedData) {
			if (CharliesUtils.isRequestForResource(group.getOriginator(), resource)) {
				out.importRecord(group);
				continue;
			}
			for (Request req : group.getChildren()) {
				if (CharliesUtils.isRequestForResource(req, resource)) {
					out.importRecord(group);
					break;
				}
			}
		}
		return out;
	}

	public String getSessionName() {
		return sessionName;
	}

	public void setSessionName(String name) {
		this.sessionName = name;
	}

	public String getTimeSpan() {
		String span = "";
		Date first, last;
		if (coreData.size() > 0) {
			first = last = coreData.exportRecord(0).getStartTime();
			for (Request r : coreData) {
				Date time = r.getStartTime();

				if (time.compareTo(first) < 0) {
					first = time;
				}
				else if (time.compareTo(last) > 0) {
					last = time;
				}
			}
			span = LabelFactory.formatDate(first) + " - " + LabelFactory.formatDate(last);
		}
		return span;
	}

	public int calculateTotalRequests(ResourceType resourceType) {
		int count = 0;
		for (Request r : coreData) {
			if (CharliesUtils.determineResourceType(r.getPath()) == resourceType) {
				count++;
			}
		}
		return count;
	}

	public int calculateDistinctRequests(ResourceType resourceType) {
		int count = 0;
		for (ResourceStats r : resourceData) {
			if (r.getResource().getResourceType() == resourceType) {
				count++;
			}
		}
		return count;
	}

}
